
/*
 * @项目名称: 模板开发
 * @FilePath: /meek/src/geometry/ellipse.js
 * @Author: kongxueyang@datatang.com
 * @Date: 2020-09-16T15:29:54+08:00
 * @LastEditors: yangxianghong@datatang.com
 * @LastEditTime: 2023-07-12 14:23:36
 * Copyright (c) 2018-2022
 */

/**
 * Created by zypc on 2016/11/13.
 */

import Geometry from './geometry'
import Extent from './extent'
import Circle from './circle';
import { ExtentUtil } from './support/extentutil';
import { Transform } from "../data/matrix/transform";
import cloneDeep from 'lodash/cloneDeep'
import { getProjectionPointOnLine } from './support/geometryutil'
import { distance } from './support/geometryutil'


export default class Ellipse extends Geometry {

  constructor (x, y, radiusX, radiusY, rotation = 0) {
    super()

    this.x = x
    this.y = y
    this.radiusX = radiusX
    this.radiusY = radiusY
    this.rotation = rotation;
    this.sAngle = 0
    this.eAngle = 2 * Math.PI
    this.counterclockwise = true
  }

  /**
   * 获取对象的几何类型
   *
   * @protected
   */
  get geometryType () { return Geometry.ELLIPSE }

  /**
   * 获取点的最小外接矩形
   *
   * @note 点是没有最小外接矩形
   * @returns {null}
   */
  get extent () {
    let radians = this.rotation;
    let radians90 = radians + Math.PI / 2;
    let ux = this.radiusX * Math.cos(radians);
    let uy = this.radiusX * Math.sin(radians);
    let vx = this.radiusY * Math.cos(radians90);
    let vy = this.radiusY * Math.sin(radians90);

    let width = Math.sqrt(ux * ux + vx * vx) * 2;
    let height = Math.sqrt(uy * uy + vy * vy) * 2;
    let x = this.x - (width / 2);
    let y = this.y - (height / 2);

    // const r = Math.max(this.radiusX, this.radiusY);
    return new Extent(x, y , x + width, y + height)
  }

  getArea() {
    return Math.PI * (this.radiusX - 0.1) * (this.radiusY - 0.1);
  }

  containsXY (x, y, opt = {}) {
    let localCoord = this.map2local(x, y);
    let localX = localCoord[0];
    let localY = localCoord[1];
    let maxY = Math.sqrt(
      (1 - Math.pow(localX, 2) / Math.pow(this.radiusX, 2)) *
      Math.pow(this.radiusY, 2)
    );
    let minY = -maxY;
    return localY <= maxY && localY >= minY;
    //return ExtentUtil.containsPoint(this.extent, [x, y]);
    // return Math.pow(x - this.x, 2) / Math.pow(this.radiusX, 2) + Math.pow(y - this.y, 2) / Math.pow(this.radiusY, 2) <= 1;
  }

  /**
   * 更新x,y坐标值
   *
   * @method update
   * @param nx x新值
   * @param ny y新值
   */
  update (coordinates) {
    ['x', 'y', 'radiusX', 'radiusY', 'rotation'].forEach((key, index) => {
      if (coordinates[index] !== undefined) {
        this[key] = key === 'rotation' ? this.decodeRotation(coordinates[index]) : coordinates[index];
      }
    })
    if (this.radiusX <= 0) {
      this.radiusX = 0.1;
    }
    if (this.radiusY <= 0) {
      this.radiusY = 0.1;
    }
    this._extent = null
  }

  updateCoord(coordinates, extent) {
    let x = 1/2 * (coordinates[0][0] + coordinates[1][0]);
    let y = 1/2 * (coordinates[0][1] + coordinates[1][1]);
    let a = 1/2 * Math.abs(coordinates[1][0] - coordinates[0][0]);
    let b = 1/2 * Math.abs(coordinates[1][1] - coordinates[0][1]);
    if (!extent) {
      this.update([x, y, a, b]);
    } else {
      if (x - a < 0 || x + a > extent[2]) {
        return ;
      }
      if (y - b < 0 || y + b > extent[3]) {
        return ;
      }
      this.update([x, y, a, b]);
    }
  }

  // 旋转/拖动
  _getModifyCoordinates(segmentData, vertex, extent) {
    let coordinates = this.getCoordinates();
    const vertexes = this.getVertexs()
    const index = segmentData.index;

    if (segmentData.index === 0 || segmentData.index === 2) {
      let newA = Circle.getDistance(vertex, [this.x, this.y]);
      let dist;
      if(index === 0) {
        dist = Circle.getDistance(vertexes[2], vertex);
      } else {
        dist = Circle.getDistance(vertexes[0], vertex);
      }
      if (newA <= 0) {
        return coordinates;
      }
      if(index === 0) {
        this.x = (vertexes[2][0] + vertex[0]) / 2;
        this.y = (vertexes[2][1] + vertex[1]) / 2;

        const a = vertex[1] - vertexes[2][1];
        const b = vertex[0] - vertexes[2][0];
        var c = -Math.atan2(a, b) + Math.PI;
      } else {
        this.x = (vertexes[0][0] + vertex[0]) / 2;
        this.y = (vertexes[0][1] + vertex[1]) / 2;

        const a = vertex[1] - vertexes[0][1];
        const b = vertex[0] - vertexes[0][0];
        var c = -Math.atan2(a, b);
      }
      if (extent) {
        const rect = this.getRectangle(newA, this.radiusY, this.x, this.y, this.rotation);
        if (rect[0][0] <= 0 || rect[1][0] >= extent[2]) {
          return coordinates;
        }
        if (rect[0][1] <= 0 || rect[1][1] >= extent[3]) {
          return coordinates;
        }
      }
      coordinates[0] = this.x;
      coordinates[1] = this.y;
      coordinates[2] =  dist / 2;
      coordinates[4] =  c;
      return coordinates;
    }
    if (segmentData.index === 1 || segmentData.index === 3) {
      let newB = Circle.getDistance(vertex, [this.x, this.y]);
      let dist;
      if(index === 1) {
        dist = Circle.getDistance(vertexes[3], vertex);
      } else {
        dist = Circle.getDistance(vertexes[1], vertex);
      }
      if (newB <= 0) {
        return coordinates;
      }

      if(index === 1) {
        this.x = (vertexes[3][0] + vertex[0]) / 2;
        this.y = (vertexes[3][1] + vertex[1]) / 2;

        const a = vertex[1] - vertexes[3][1];
        const b = vertex[0] - vertexes[3][0];
        var c = Math.atan2(b, a);
      } else {
        this.x = (vertexes[1][0] + vertex[0]) / 2;
        this.y = (vertexes[1][1] + vertex[1]) / 2;

        const a = vertex[1] - vertexes[1][1];
        const b = vertex[0] - vertexes[1][0];
        var c = Math.atan2(b, a) + Math.PI;
      }
      if (extent) {
        const rect = this.getRectangle(this.radiusX, newB, this.x, this.y, this.rotation);
        if (rect[0][0] <= 0 || rect[1][0] >= extent[2]) {
          return coordinates;
        }
        if (rect[0][1] <= 0 || rect[1][1] >= extent[3]) {
          return coordinates;
        }
      }
      coordinates[0] = this.x;
      coordinates[1] = this.y;
      coordinates[3] =  dist / 2;
      coordinates[4] =  c;
      return coordinates;
    }
  }

  // 拖拽保持另一个点不动
  getModifyCoordinates(segmentData, vertex, extent) {
    let coordinates = this.getCoordinates();
    const vertexes = this.getVertexs();
    let newVertex = null;
    const index = segmentData.index;

    if (segmentData.index === 0 || segmentData.index === 2) {
      newVertex = getProjectionPointOnLine(vertexes[0], vertexes[2], vertex);
      let dist;
      let newA = Circle.getDistance(vertex, [this.x, this.y]);
      if (newA <= 0) {
        return coordinates;
      }

      if(index === 0) {
        // if (newVertex[0] + 1 >= vertexes[2][0]) {
        //   return coordinates;
        // }
        dist = Circle.getDistance(vertexes[2], newVertex);
        if(dist < 2) {
          return coordinates;
        }
        this.x = (vertexes[2][0] + newVertex[0]) / 2;
        this.y = (vertexes[2][1] + newVertex[1]) / 2;
      } else {
        // if (newVertex[0] - 1 <= vertexes[0][0]) {
        //   return coordinates;
        // }
        dist = Circle.getDistance(vertexes[0], newVertex);
        if(dist < 2) {
          return coordinates;
        }
        this.x = (vertexes[0][0] + newVertex[0]) / 2;
        this.y = (vertexes[0][1] + newVertex[1]) / 2;
      }

      if (extent) {
        const rect = this.getRectangle(dist / 2, this.radiusY, this.x, this.y, this.rotation);
        if (rect[0][0] <= 0 || rect[1][0] >= extent[2]) {
          return coordinates;
        }
        if (rect[0][1] <= 0 || rect[1][1] >= extent[3]) {
          return coordinates;
        }
      }
      coordinates[0] = this.x;
      coordinates[1] = this.y;
      coordinates[2] =  dist / 2;
      return coordinates;
    }
    if (segmentData.index === 1 || segmentData.index === 3) {
      newVertex = getProjectionPointOnLine(vertexes[1], vertexes[3], vertex);
      let newB = Circle.getDistance(vertex, [this.x, this.y]);
      let dist;
      if (newB <= 0) {
        return coordinates;
      }
      if(index === 1) {
        // if (newVertex[1] - 1 <= vertexes[3][1]) {
        //   return coordinates;
        //   }
        dist = Circle.getDistance(vertexes[3], newVertex);
        if(dist < 2) {
          return coordinates;
        }
        this.x = (vertexes[3][0] + newVertex[0]) / 2;
        this.y = (vertexes[3][1] + newVertex[1]) / 2;
      } else {
        // if (newVertex[1] + 1 >= vertexes[1][1]) {
        //   return coordinates;
        //   }
        dist = Circle.getDistance(vertexes[1], newVertex);
        if(dist < 2) {
          return coordinates;
        }
        this.x = (vertexes[1][0] + newVertex[0]) / 2;
        this.y = (vertexes[1][1] + newVertex[1]) / 2;
      }
      if (extent) {
        const rect = this.getRectangle(this.radiusX, dist / 2, this.x, this.y, this.rotation);
        if (rect[0][0] <= 0 || rect[1][0] >= extent[2]) {
          return coordinates;
        }
        if (rect[0][1] <= 0 || rect[1][1] >= extent[3]) {
          return coordinates;
        }
      }

        coordinates[0] = this.x;
        coordinates[1] = this.y;
        coordinates[3] =  dist / 2;
        return coordinates;
    }
  }
  // 拖拽
  getModifyCoordinates1(segmentData, vertex, extent) {
    let coordinates = this.getCoordinates();

    if (segmentData.index === 0 || segmentData.index === 2) {

      let newA = Circle.getDistance(vertex, [this.x, this.y]);
      if (newA <= 0) {
        return coordinates;
      }
      if (extent) {
        const rect = this.getRectangle(newA, this.radiusY, this.x, this.y, this.rotation);
        if (rect[0][0] <= 0 || rect[1][0] >= extent[2]) {
          return coordinates;
        }
        if (rect[0][1] <= 0 || rect[1][1] >= extent[3]) {
          return coordinates;
        }
      }
      coordinates[2] = newA;
      return coordinates;
    }
    if (segmentData.index === 1 || segmentData.index === 3) {
      let newB = Circle.getDistance(vertex, [this.x, this.y]);
      if (newB <= 0) {
        return coordinates;
      }
      if (extent) {
        const rect = this.getRectangle(this.radiusX, newB, this.x, this.y, this.rotation);
        if (rect[0][0] <= 0 || rect[1][0] >= extent[2]) {
          return coordinates;
        }
        if (rect[0][1] <= 0 || rect[1][1] >= extent[3]) {
          return coordinates;
        }
      }
      coordinates[3] = newB;
      return coordinates;
    }
  }

  /**
   *
   *
   * getFlatInteriorPoint
   * @returns {[]}
   */
  getFlatInteriorPoint () {
    return [this.x, this.y - this.radiusY];
  }

  getCoordinates () {
    return [this.x, this.y, this.radiusX, this.radiusY, this.decodeRotation(this.rotation)];
  }

  //顺时针互转逆时针弧度[0, 2π]
  decodeRotation(value) {
    const pi = Math.PI;
    return (2 * pi - value % (2 * pi)) % (2 * pi);
  }
  /**
   * 设置点的坐标
   *
   * @method setCoordinates
   * @param coordinates
   */
  setCoordinates (coordinates) {
    this.update(coordinates)
    this.changed()
  }


  /**
   * 判断两个图形对象是否相等
   *
   * 判断标准：该图形的所有顶点值是否一致
   *
   * @method equal
   * @param geometry
   * @return {Boolean}
   */
  equal (geom) {
    let equals = false
    if (geom !== null) {
      return this.x === geom.x && this.y === geom.y && this.radiusX === geom.radiusX && this.radiusY === geom.radiusY;
    }
    return equals
  }

  /**
   * 克隆点
   *
   * @method clone
   * @returns {Point}
   */
  clone () {
    return new Ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation)
  }

  getFormShowPosition() {
    return [this.x + this.radiusX, this.y];
  }

  getDistance(cood) {
    return {
      a: Math.abs(cood[0] - this.x),
      b: Math.abs(cood[1] - this.y)
    }
  }


  getVertexs() {
    let cX = this.x, cY = this.y;
    //四个顶点坐标(x1,y1),(x2,y2),(x3,y3),(x4,y4)
    var x1 = this.x - this.radiusX,
      y1 = this.y - this.radiusY;
    var x2 = this.x - this.radiusX,
      y2 = this.y + this.radiusY;
    var x3 = this.x + this.radiusX,
      y3 = this.y + this.radiusY;
    var x4 = this.x + this.radiusX,
      y4 = this.y - this.radiusY;

    var sin = Math.sin(this.rotation);

    var cos = Math.cos(this.rotation);
    //求旋转后的四个点(x11,y11),(x22,y22),(x33,y33),(x44,y44),(x55,y55)
    var x11 = (x1 - cX) * cos - (y1 - cY) * sin + cX;
    var y11 = (x1 - cX) * sin + (y1 - cY) * cos + cY;

    var x22 = (x2 - cX) * cos - (y2 - cY) * sin + cX;
    var y22 = (x2 - cX) * sin + (y2 - cY) * cos + cY;

    var x33 = (x3 - cX) * cos - (y3 - cY) * sin + cX;
    var y33 = (x3 - cX) * sin + (y3 - cY) * cos + cY;

    var x44 = (x4 - cX) * cos - (y4 - cY) * sin + cX;
    var y44 = (x4 - cX) * sin + (y4 - cY) * cos + cY;

    return [
      [(x11 + x22) / 2, (y11 + y22) / 2],
      [(x22 + x33) / 2, (y22 + y33) / 2],
      [(x33 + x44) / 2, (y33 + y44) / 2],
      [(x44 + x11) / 2, (y44 + y11) / 2]
    ]
  }

  getVertex(pixelCoordinate, tolerance) {
    let canEditCoords = this.getVertexs();
    let index = -1;
    let min = tolerance;
    canEditCoords.forEach((item, i) => {
      if (Circle.getDistance(item, pixelCoordinate) <= min) {
        min = Circle.getDistance(item, pixelCoordinate);
        index = i
      }
    })
    return {
      index: index,
      coords: canEditCoords[index]
    }
  }

  move(x = 0, y = 0, opts) {
    let beyond
    if (opts) {
      if (opts.beyond) {
        beyond = opts.beyond
      }
    }
    let radiusX = this.radiusX;
    let radiusY = this.radiusY;
    if (beyond) {
      const rect = this.getRectangle(this.radiusX, this.radiusY, this.x + x, this.y + y, this.rotation);
      if (rect[0][0] < beyond.xmin || rect[1][0] > beyond.xmax) {
        // if (rect[0][0] < beyond.xmin) {
        //   this.x = beyond.xmin + radiusX - x;
        // }
        // if (this.x + x + radiusX > beyond.xmax) {
        //   this.x = beyond.xmax - radiusX - x;
        // }
      } else {
        this.x += x;
      }

      if (rect[0][1] < beyond.ymin || rect[1][1] > beyond.ymax) {
        // if (this.y + y - radiusY < beyond.ymin) {
        //   this.y = beyond.ymin + radiusY - y;
        // }
        // if (this.y + y + radiusY > beyond.ymax) {
        //   this.y = beyond.ymax - radiusY - y;
        // }
      } else {
        this.y += y;
      }
    } else {
      this.x += x;
      this.y += y;
    }
  }

  rotate(extent, mouseDownX, mouseDownY, mouseMoveX, mouseMoveY) {
    let cX = this.x;
    let cY = this.y;
    let mX1 = mouseDownX;
    let mY1 = mouseDownY;
    //鼠标移动点(mX2,mY2)
    let mX2 = mouseMoveX;
    let mY2 = mouseMoveY;
    //旋转角度cos(θ) = cos;sin(θ) = sin;
    //通过向量的点积进行求解
    let abx = cX - mX1;
    let aby = cY - mY1;
    let acx = cX - mX2;
    let acy = cY - mY2;
    //叉乘求sin(θ)
    let abxac = abx * acy - acx * aby;
    let model = Math.sqrt(abx * abx + aby * aby) * Math.sqrt(acx * acx + acy * acy);
    let sin = abxac / model;
    //点乘求cos(θ)
    let abac = abx * acx + aby * acy;
    let cos = abac / model;

    let radius = Math.atan(sin / cos);

    if (extent) {
      const rect = this.getRectangle(this.radiusX, this.radiusY, this.x, this.y, this.rotation + radius);
      if (rect[0][0] <= 0 || rect[1][0] >= extent[2]) {
        return ;
      }
      if (rect[0][1] <= 0 || rect[1][1] >= extent[3]) {
        return ;
      }
    }

    this.rotation = this.rotation + radius;
  }

/**
 * @description: 计算椭圆外接矩形（https://zhuanlan.zhihu.com/p/82184417）
 * @param {*}
 * @return {*}
 */

  getRectangle(a, b, x, y, rotation) {
    let [A, B, C, F] = this.getEllipseParam(a, b, rotation);
    let [p1, p2] = this.calculateRectangle(A, B, C, F);
    return [[x + p1[0], y + p1[1]], [x + p2[0], y + p2[1]]];
  }
  getEllipseParam(a, b, angle) {
    let sin = Math.sin(-angle);
    let cos = Math.cos(-angle);
    let A = Math.pow(a, 2) * Math.pow(sin, 2) + Math.pow(b, 2) * Math.pow(cos, 2);
    let B = 2 * (Math.pow(a, 2) - Math.pow(b, 2)) * sin * cos
    let C = Math.pow(a, 2) * Math.pow(cos, 2) + Math.pow(b, 2) * Math.pow(sin, 2)
    let F = -Math.pow(a, 2) * Math.pow(b, 2)
    return [A, B, C, F];
  }
  calculateRectangle(A, B, C, F) {
    let y = Math.sqrt(4 * A * F / (Math.pow(B, 2) - 4 * A * C));
    let y1 = -Math.abs(y);
    let y2 =  Math.abs(y);

    let x = Math.sqrt(4 * C * F / (Math.pow(B, 2) - 4 * C * A));
    let x1 = -Math.abs(x);
    let x2 = Math.abs(x);

    return [[x1, y1], [x2, y2]];
  }

  map2local(x, y) {
    let coord = [x, y];
    const transform = Transform.create();
    // 平移矩阵
    Transform.set(transform,
     //1, 0, -this.x,
     //0, 1, -this.y,
     1, 0, 0, 1, -this.x, -this.y
    )

    Transform.apply(transform, coord);

    const angle = this.rotation;
    Transform.set(transform,
      //Math.cos(-angle), -Math.sin(-angle), 0,
      //Math.sin(-angle), Math.cos(-angle), 0,
      Math.cos(-angle), Math.sin(-angle), -Math.sin(-angle), Math.cos(-angle), 0, 0
    )
    Transform.apply(transform, coord);
    return coord;
  }

  getTransform(frameState, offsetX) {
    const viewState = frameState.viewState
    const pixelRatio = 1
    const dx1 = pixelRatio * frameState.size[0] / 2
    const dy1 = pixelRatio * frameState.size[1] / 2
    const sx = pixelRatio / viewState.resolution
    const sy = sx
    const angle = -viewState.rotation
    const dx2 = -viewState.center[0] + offsetX
    const dy2 = -viewState.center[1]
    return Transform.compose(ExtentUtil.createEmpty(), dx1, dy1, sx, sy, angle, dx2, dy2)
  }

  intersectPoint(x, y, tolerance = 3, type = 1) {
    const points = this.getVertexs();
    if(type === 1) {
      const point = points.find(item => {
        const pointDistance = distance([x, y], item)
        if (pointDistance <= tolerance) {
          return true
        }
      })
      return point;
    }

    if(type === 2) {
      // TODO
    }
  }
}
